module Saulabs

  module Reportable

    # The grouping specifies which records are grouped into one {Saulabs::Reportable::ReportingPeriod}.
    #
    class Grouping

      # Initializes a new grouping.
      #
      # @param [Symbol] identifier
      #   the identifier of the grouping (one of +:hour+, +:day+, +:week+ or +:month+)
      #
      def initialize(identifier)
        raise ArgumentError.new("Invalid grouping #{identifier}") unless [:hour, :day, :week, :month].include?(identifier)
        @identifier = identifier
      end

      # Gets the identifier of the grouping.
      #
      # @return [Symbol]
      #   the identifier of the grouping.
      #
      def identifier
        @identifier
      end

      # Gets an array of date parts from a DB string.
      #
      # @param [String] db_string
      #   the DB string to get the date parts from
      #
      # @return [Array<Fixnum>]
      #   array of numbers that represent the values of the date
      #
      def date_parts_from_db_string(db_string)
        case ActiveRecord::Base.connection.adapter_name
          when /mysql/i
            from_mysql_db_string(db_string)
          when /sqlite/i
            from_sqlite_db_string(db_string)
          when /postgres/i
            from_postgresql_db_string(db_string)
          when /mssql/i, /sqlserver/i
            from_sqlserver_db_string(db_string)
        end
      end

      # Converts the grouping into a DB specific string that can be used to group records.
      #
      # @param [String] date_column
      #   the name of the DB column that holds the date
      #
      def to_sql(date_column)
        case ActiveRecord::Base.connection.adapter_name
          when /mysql/i
            mysql_format(date_column)
          when /sqlite/i
            sqlite_format(date_column)
          when /postgres/i
            postgresql_format(date_column)
          when /mssql/i, /sqlserver/i
            sqlserver_format(date_column)
        end
      end

      private

        def from_mysql_db_string(db_string)
          if @identifier == :week
            parts = [db_string[0..3], db_string[4..5]].map(&:to_i)
          else
            db_string.split(@identifier == :day ? '-' : '/').map(&:to_i)
          end
        end

        def from_sqlite_db_string(db_string)
          if @identifier == :week
            parts = db_string.split('-').map(&:to_i)
            date = Date.new(parts[0], parts[1], parts[2])
            return [date.cwyear, date.cweek]
          end
          db_string.split('/').map(&:to_i)
        end

        def from_postgresql_db_string(db_string)
          case @identifier
            when :hour
              return (db_string[0..9].split('-') + [db_string[11..12]]).map(&:to_i)
            when :day
              return db_string[0..9].split('-').map(&:to_i)
            when :week
              parts = db_string[0..9].split('-').map(&:to_i)
              date = Date.new(parts[0], parts[1], parts[2])
              return [date.cwyear, date.cweek]
            when :month
              return db_string[0..6].split('-')[0..1].map(&:to_i)
          end
        end

        def from_sqlserver_db_string(db_string)
          if @identifier == :week
            parts = [db_string[0..3], db_string[5..6]].map(&:to_i)
          else
            db_string.split(/[- ]/).map(&:to_i)
          end
        end

        def mysql_format(date_column)
          case @identifier
            when :hour
              "DATE_FORMAT(#{date_column}, '%Y/%m/%d/%H')"
            when :day
              "DATE(#{date_column})"
            when :week
              "YEARWEEK(#{date_column}, 3)"
            when :month
              "DATE_FORMAT(#{date_column}, '%Y/%m')"
          end
        end

        def sqlite_format(date_column)
          case @identifier
            when :hour
              "strftime('%Y/%m/%d/%H', #{date_column})"
            when :day
              "strftime('%Y/%m/%d', #{date_column})"
            when :week
              "date(#{date_column}, 'weekday 0')"
            when :month
              "strftime('%Y/%m', #{date_column})"
          end
        end

        def postgresql_format(date_column)
          case @identifier
            when :hour
              "date_trunc('hour', #{date_column})"
            when :day
              "date_trunc('day', #{date_column})"
            when :week
              "date_trunc('week', #{date_column})"
            when :month
              "date_trunc('month', #{date_column})"
          end
        end

        def sqlserver_format(date_column)
          case @identifier
            when :hour
              "DATEADD(hh,DATEDIFF(hh,DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900'),#{date_column}), DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900'))"
            when :day
              "DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900')"
            when :week
              "LEFT(CONVERT(varchar,#{date_column},120), 4) + '-' + CAST(DATEPART(isowk,#{date_column}) AS VARCHAR)"
            when :month
              "DATEADD(mm,DATEDIFF(mm,'1 Jan 1900',#{date_column}), '1 Jan 1900')"
          end
        end

    end

  end

end
